/**
 * This class contains methods for the communication with SIM card.
 * GsmCardService extends JSmexCardService and implements all its abstract methods.
 *
 *  This file is part of JSmex.
 *
 *  JSmex is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JSmex is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jsmex.cardservice;

import java.util.Vector;
import jsmex.function.FileProperties;
import jsmex.function.SmartCardFile;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.terminal.CardTerminalException;
import opencard.core.util.HexString;
import jsmex.JSmexTools;


/**
 * @author Tobias Senger
 *
 */


public class GsmCardService extends JSmexCardService {
    
    private static CommandAPDU command = new CommandAPDU(50);
    private static ResponseAPDU response;
    private static byte[] selectedFID = new byte[2];
    private boolean chv1Verified = false;
    private boolean chv2Verified = false;
    
    
    public GsmCardService() {
        super();
    }
    
    /** This method returns the ResponseAPDU of the last command which was sent to the card.
     *
     * @return The ResponseAPDU of the last command which was sent to the card.
     */
    public ResponseAPDU getLastResponse() {
        lt.info("called method getLastResponse()",this);
        return response;
    }
    
    /** This method returns the FID of the currently selected EF or DF.
     *
     * @return An byte-array which contains the FID of the currently selected EF or DF.
     */
    public byte[] getSelectedFID() {
        byte[] status_cmd = { (byte)0xA0, (byte)0xF2, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x06 };;
        command.setLength(0);
        command.append(status_cmd);
        try {
            response = sendCommandAPDU(command);
            selectedFID[0] = (byte)response.getByte(4);
            selectedFID[1] = (byte)response.getByte(5);
            lt.info("called method getSelectedFID(). returns: "+HexString.hexify(selectedFID),this);
            
        } catch (CardTerminalException cte) {
            lt.warning("method getSelectedFID() throws a CardTerminalException.",this);
        }
        return selectedFID;
    }
    
    
    /** This method returns true if the PIN is verified.
     *
     * @param chvindicator This argument contaisn the number of the PIN (1 or 2).
     * @return Returns true if the PIN is verified. Otherwise false.
     */
    public boolean chvIsVerified(int chvindicator) {
        lt.info("called method chvIsVerified("+Integer.toString(chvindicator)+").",this);
        if (chvindicator==1) return chv1Verified;
        if (chvindicator==2) return chv2Verified;
        return false;
    }
    
    /**
     * This function selects a file (DF or EF) on the card.
     * @param fid The FID to select in an byte-array.
     * @return ResponseAPDU of the command.
     */
    public ResponseAPDU selectFID(byte[] fid) {
        byte[] select_cmd= {(byte)0xA0, (byte)0xA4, (byte)0x00, (byte)0x00, (byte)0x02};
        byte[] get_response = { (byte)0xA0, (byte)0xC0, (byte)0x00, (byte)0x00 };
        command.setLength(0);
        command.append(select_cmd);
        command.append(fid);
        try {
            response = sendCommandAPDU(command);
            if (response.sw1() == (byte)0x9F) {
                command.setLength(0);
                command.append(get_response);
                command.append(response.sw2());
                response = sendCommandAPDU(command);
                if (response.sw() == 0x9000) {
                    
                    selectedFID[0] = (byte)response.getByte(4);
                    selectedFID[1] = (byte)response.getByte(5);
                }
            }
        } catch (CardTerminalException cte) {
            lt.warning("method selectFID() throws a CardTerminalException ",this);
        }
        lt.info("called method selectFID( FID:"+HexString.hexify(fid)+" ). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This function selects a file (DF or EF)
     * @param fid The FID to select in a String.
     * @return ResponseAPDU of the command.
     */
    public ResponseAPDU selectFID(String fid) {
        lt.info("called method selectFID(String fid)",this);
        return selectFID(JSmexTools.parseHexString(fid));
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It select the a EF whose FID comes in parameter fid.
     *
     * @param fid The FID to be selected in an byte-array.
     * @return Returns the ReponseAPDU of the select commando.
     */
    public ResponseAPDU selectEF(byte[] fid) {
        lt.info("called method selectEF("+HexString.hexify(fid)+")",this);
        return selectFID(fid);
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It select the a DF whose FID comes in parameter fid.
     *
     * @param fid The FID to be selected in an byte-array.
     * @return Returns the ReponseAPDU of the select commando.
     */
    public ResponseAPDU selectDF(byte[] fid) {
        lt.info("called method selectDF("+HexString.hexify(fid)+")",this);
        return selectFID(fid);
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It selects the MF of the card.
     *
     * @return Returns 'true' if everything went well,
     * otherwise 'false' will be returned.
     */
    public boolean selectMF() {
        
        if (selectFID("3F 00").sw()==0x9000) {
            lt.info("called method selectMF(). return is \"true\"", this);
            return true;
        } else {
            lt.info("called method selectMF(). return is \"false\"", this);
            return false;
        }
    }
    
    /**
     * This method reads the current selected tranparent EF
     * @param oh offset high
     * @param ol offset low
     * @param length The length of data to read
     * @return ResponseAPDU
     */
    public ResponseAPDU readBinary(byte oh, byte ol, byte length) {
        byte[] rb_cmd = { (byte)0xA0, (byte)0xB0, (byte)oh, (byte)ol, (byte)length};
        command.setLength(0);
        command.append(rb_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method readBinary(oh: "+HexString.hexify(oh)+", ol: "+HexString.hexify(ol)+", length: "+HexString.hexify(length)+
                    ") throws CardTerminalExecption.",this);
        }
        lt.info("called method readBinary(oh: "+HexString.hexify(oh)+", ol: "+HexString.hexify(ol)+", length: "+HexString.hexify(length)+
                ")."+" returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * Updates the current selected transparent EF
     * @param oh offset high
     * @param ol offset low
     * @param data Data to write
     * @return ResponseAPDU
     */
    public ResponseAPDU updateBinary(byte oh, byte ol, byte[] data) {
        byte[] ub_cmd = { (byte)0xA0, (byte)0xD6, (byte)oh, (byte)ol, (byte)data.length};
        command.setLength(0);
        command.append(ub_cmd);
        command.append(data);
        
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method updateBinary(byte oh, byte ol, byte[] data) throws CardTerminalExecption.",this);
        }
        lt.info("called method updateBinary(byte oh, byte ol, byte[] data)."+" returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**This method checks if the EFs and DFs which are get in parameter filearray.
     * If the file exist, an new instance of SmartCardFile will builded.
     * All founded SmartCardFiles will be returned in an array.
     *
     * @param filearray A two-dimensional array which contains FIDs and names of EFs and DFs.
     * @return An array of SmartCardFile.
     */
    private SmartCardFile[] buildFileList(String[][] filearray) {
        
        ResponseAPDU resp = null;
        SmartCardFile[] scfiles = null;
        Vector filevec = new Vector();
        boolean addUsim = false;
        
        lt.info("called method buildFileList(String[][] filearray)", this);
        
        for (int i=0; i<filearray.length;i++){
            try {
                resp = selectFID(filearray[i][1]);      // test if the file exist
                if (resp.sw1()==(byte)0x90) {           // if so then get the properties of the file
                    FileProperties properties = getProperties(resp);
                    SmartCardFile tempfile = new SmartCardFile(filearray[i][0], properties);
                    filevec.add(tempfile);
                    
                }
            } catch (Exception exc) {}
        }
        
        scfiles = new SmartCardFile[filevec.size()];
        scfiles =  (SmartCardFile[]) filevec.toArray(scfiles);
        return scfiles;
    }
    
    /**This method returns an array of SmartCardFile which contains all founded
     * files under the DF which ist specified in parameter scfile.
     *
     * @param scfile Contains a SmartCardFile instance of a DF.
     * @return An array of SmartCardFile.
     */
    public SmartCardFile[] getFileList(SmartCardFile scfile) {
        
        lt.info("called method getFileList(SmartCardFile scfile). scfile name is"+scfile.toString()+". scfile FID is"+HexString.hexify(scfile.fp.getFID()), this);
        
        if (scfile.fp.getFID()[0]==(byte)0x3F && scfile.fp.getFID()[1]==(byte)0x00) {                 //---------MF--------------
            String[][] mffiles = {{"EF DIR","2F 00"},{"EF ICCID","2F E2"},{"EF ELP","2F 05"},{"EF ARR","2F 06"},{"DF GSM","7F 20"},{"DF TELECOM","7F 10"}};
            
            try {
                selectMF();
            } catch (Exception exc) {}
            
            return buildFileList(mffiles);
        }
        
        else if (scfile.fp.getFID()[0]==(byte)0x7F && scfile.fp.getFID()[1]==(byte)0x10) {            //---------DF TELECOM--------------
            String[][] dftelecomfiles = {{"EF ADN","6F 3A"},{"EF FDN","6F 3B"},{"EF SMS","6F 3C"},{"EF CCP","6F 3D"},{"EF MSISDN","6F 40"},{"EF SMSP","6F 42"},{"EF SMSS","6F 43"}
            ,{"EF LND","6F 44"},{"EF EXT1","6F 4A"},{"EF EXT2","6F 4B"},{"EF SDN","6F 49"}};
            
            try {
                selectFID("7F 10");
            } catch (Exception exc) {}
            
            return buildFileList(dftelecomfiles);
        }
        
        else if (scfile.fp.getFID()[0]==(byte)0x7F && scfile.fp.getFID()[1]==(byte)0x20) {            //---------DF GSM--------------
            String[][] dfgsmfiles = {{"EF LP","6F 05"},{"EF IMSI","6F 07"},{"EF KC","6F 20"},{"EF PLMsel","6F 30"},{"EF HPLMN","6F 31"},{"EF ACMmax","6F 37"},{"EF SST","6F 38"},{"EF ACM","6F 39"},{"EF GID1","6F 3E"}
            ,{"EF GID2","6F 3F"},{"EF PUCT","6F 41"},{"EF CBMI","6F 45"},{"EF SPN","6F 46"},{"EF BCCH","6F 74"},{"EF ACC","6F 78"},{"EF FPLMN","6F 7B"},{"EF LOCI","6F 7E"},{"EF AD","6F AD"},{"EF PHASE","6F AE"}
            ,{"DF Graphics","5F 50"}};
            
            try {
                selectFID("7F 20");
            } catch (Exception exc) {}
            
            return buildFileList(dfgsmfiles);
        } else if (scfile.fp.getFID()[0]==(byte)0x5F && scfile.fp.getFID()[1]==(byte)0x50) {            //---------DF GSM.DF Graphics--------------
            String[][] dfgsmfiles = {{"EF IMG","4F 20"},{"EF IMGDataX","4F 21"}};
            
            try {
                selectFID("5F 50");
            } catch (Exception exc) {}
            
            return buildFileList(dfgsmfiles);
        }
        
        else return null;
        
    }
    
    /**This method implements the abstract method from JSmexCardService.
     * It reads one complete record of the currently selected linear or cyclic EF.
     *
     * @return Returns a ResponseAPDU.
     */
    public ResponseAPDU readRecord(byte recno, int length) {
        byte recordLength = (byte) length;
        byte[] rr_cmd = { (byte)0xA0, (byte)0xB2, (byte)recno, (byte)0x04, (byte)recordLength};
        command.setLength(0);
        command.append(rr_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method readRecord(recno: "+HexString.hexify(recno)+", length: "+Integer.toString(length)+") throws an CardTerminalException.",this);
        }
        lt.info("called method readRecord(recno:"+HexString.hexify(recno)+", length:"+Integer.toString(length)+")",this);
        return response;
    }
    
    
    
    /**
     * This function reads one complete record in the current linear fixed or cyclic EF.
     * @param recno record number (absolute mode only)
     * @param mode Define the read mode: <ul>
     *          <li>0x02: next record</li>
     * 		<li>0x03: previous record</li>
     * 		<li>0x04: absolute mode, record number is given in recNo;</li>
     *          </ul>
     * @param recordLength The record length of the record.
     * @return The ResponseAPDU
     */
    public ResponseAPDU readRecord(byte recno, byte mode, byte recordLength) {
        byte[] rr_cmd = { (byte)0xA0, (byte)0xB2, (byte)recno, (byte)mode, (byte)recordLength};
        command.setLength(0);
        command.append(rr_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method readRecord(recno:"+HexString.hexify(recno)+", mode: "+HexString.hexify(mode)+", recordLength:"+Integer.toString(recordLength)+") throws an CardTerminalException.",this);
        }
        lt.info("called method readRecord(recno:"+HexString.hexify(recno)+", mode: "+HexString.hexify(mode)+", recordLength:"+Integer.toString(recordLength)+")",this);
        return response;
    }
    
    
    /**
     * This function verifies the PIN
     * @param chvindication PIN indication (1 or 2)
     * @param chv The PIN to verify as String
     * @return ResponseAPDU
     */
    public ResponseAPDU verifyCHV(byte chvindication, String chv){
        byte[] verify_cmd = {(byte)0xA0, (byte)0x20, (byte)0x00, (byte)chvindication, (byte)0x08};
        command.setLength(0);
        command.append(verify_cmd);
        command.append(JSmexTools.stringToPin(chv));
        
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method verifyCHV(byte chvindication, String chv) throws an CardTerminalException.",this);
        }
        if (response.sw()==0x9000) {
            if (chvindication==(byte)0x01) chv1Verified = true;
            if (chvindication==(byte)0x02) chv2Verified = true;
        }
        lt.info("called method verifyCHV(chvindication: "+HexString.hexify(chvindication)+", chv: "+chv+"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
        
    }
    
    /**
     * This method assigns an new value to the relevant PIN
     * @param chvindication PIN indication (1 or 2)
     * @param ochv old PIN
     * @param nchv new PIN
     * @return The ResponseAPDU
     */
    public ResponseAPDU changeCHV(byte chvindication, String ochv, String nchv) {
        byte[] cc_cmd = { (byte)0xA0, (byte)0x24, (byte)0x00, chvindication, (byte)0x10};
        command.setLength(0);
        command.append(cc_cmd);
        command.append(JSmexTools.stringToPin(ochv));
        command.append(JSmexTools.stringToPin(nchv));
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method changeCHV(byte chvindication, String ochv, String nchv) throws an CardTerminalException.",this);
        }
        lt.info("called method changeCHV(chvindication: "+HexString.hexify(chvindication)+", ochv: "+ochv+", nchv: "+nchv+"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method disables the PIN1
     * @param chv1 PIN1 as String
     * @return ResponseAPDU
     */
    public ResponseAPDU disableCHV(String chv1) {
        byte[] dc_cmd = { (byte)0xA0, (byte)0x26, (byte)0x00, (byte)0x01, (byte)0x08};
        command.setLength(0);
        command.append(dc_cmd);
        command.append(JSmexTools.stringToPin(chv1));
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method disableCHV(String chv1) throws an CardTerminalException.",this);
        }
        lt.info("called method disableCHV(chv1: "+chv1+"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method enables the PIN1
     * @param chv1 PIN1 as String
     * @return ResponseAPDU
     */
    public ResponseAPDU enableCHV(String chv1) {
        byte[] ec_cmd = { (byte)0xA0, (byte)0x28, (byte)0x00, (byte)0x01, (byte)0x08};
        command.setLength(0);
        command.append(ec_cmd);
        command.append(JSmexTools.stringToPin(chv1));
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method enableCHV(String chv1) throws an CardTerminalException.",this);
        }
        lt.info("called method enableCHV(chv1: "+chv1+"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method unblocks a PIN which has been blocked
     * @param chvIndex PIN indicator (1 or 2)
     * @param unblockChv The unblock chv(PUK) value as String
     * @param newChv Th new PIN value as String
     * @return ResponseAPDU
     */
    public ResponseAPDU unblockCHV(byte chvIndex, String unblockChv, String newChv) {
        if (chvIndex==(byte)1) chvIndex = (byte)0;  //This is different vom other commands. 0 does mean CHV1.
        byte[] uc_cmd = { (byte)0xA0, (byte)0x2C, (byte)0x00, chvIndex, (byte)0x10};
        command.setLength(0);
        command.append(uc_cmd);
        command.append(JSmexTools.stringToPin(unblockChv));
        command.append(JSmexTools.stringToPin(newChv));
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method unblockCHV(byte chvIndex, String unblockChv, String newChv) throws an CardTerminalException.",this);
        }
        lt.info("called method unblockCHV(chvIndex: "+HexString.hexify(chvIndex)+", unblockChv: "+unblockChv+",newChv: "+newChv+"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method invalidates the current EF
     * @return ResponseAPDU
     */
    public ResponseAPDU invalidateEF() {
        byte[] ie_cmd = { (byte)0xA0, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x00};
        command.setLength(0);
        command.append(ie_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method invalidateEF() throws an CardTerminalException.",this);
        }
        lt.info("called method invalidateEF(). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method rehabilitates the invalidated current EF
     * @return ResponseAPDU
     */
    public ResponseAPDU rehabilitateEF() {
        byte[] re_cmd = { (byte)0xA0, (byte)0x44, (byte)0x00, (byte)0x00, (byte)0x00};
        command.setLength(0);
        command.append(re_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method rehabilitateEF() throws an CardTerminalException.",this);
        }
        lt.info("called method rehabilitateEF(). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /**
     * This method runs the GSM-Algorithm to calculate the chipher key und the SRES
     * @param rand random number
     * @return ResponseAPDU
     */
    public ResponseAPDU runGsmAlgorithm(byte[] rand) {
        byte[] rg_cmd = { (byte)0xA0, (byte)0x88, (byte)0x00, (byte)0x00, (byte)0x10};
        byte[] get_response = { (byte)0xA0, (byte)0xC0, (byte)0x00, (byte)0x00 };
        command.setLength(0);
        command.append(rg_cmd);
        command.append(rand);
        try {
            response = sendCommandAPDU(command);
            if (response.sw1() == (byte)0x9F) {
                command.setLength(0);
                command.append(get_response);
                command.append(response.sw2());
                response = sendCommandAPDU(command);
            }
        } catch (CardTerminalException cte) {
            lt.warning("method runGsmAlgorithm(byte[] rand) throws an CardTerminalException.",this);
        }
        lt.info("called method runGsmAlgorithm("+ HexString.hexify(rand) +"). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
    
    /** This method reads out the properties from the ResponseAPDU from a selectFID() command
     *
     * @param resp The ResponseAPDU from a selectFID() command
     * @return Returns an instance of FileProperties
     */
    private FileProperties getProperties(ResponseAPDU resp) {
        
        lt.info("called method getProperties(ResponseAPDU resp)", this);
        
        if (resp.getByte(6)==4) { // Response from a EF
            
            int efFileSize = resp.getByte(2)*256+resp.getByte(3);
            
            byte [] fid = new byte[2];
            fid[0] = (byte) resp.getByte(4);
            fid[1] = (byte) resp.getByte(5);
            
            int updateAccess = resp.getByte(8)&0x0F;
            int readAccess = (resp.getByte(8)&0xF0) >>> 4;
            int increaseAccess = resp.getByte(9)&0x0F;
            int invalidateAccess = resp.getByte(10)&0x0F;
            int rehabilitateAccess = (resp.getByte(10)&0xF0) >>> 4;
            
            boolean isInvalidated;
            if ((resp.getByte(11)&0x01)==0x01) isInvalidated = false;
            else isInvalidated = true;
            
            int dataLength = resp.getByte(12);
            
            int fileStructure = resp.getByte(13);
            int tof = 0;
            switch (fileStructure) {
                case 0: tof = 1; break;
                case 1: tof = 2; break;
                case 3: tof = 6; break;
                
            }
            
            int recordLength = resp.getByte(14);
            int records = 0;
            if (recordLength!=0) records = efFileSize/recordLength;
            
            FileProperties fp = new FileProperties(efFileSize, tof, recordLength, records, fid, (byte)readAccess, (byte)updateAccess);
            return fp;
        } else if (resp.getByte(6)==1||resp.getByte(6)==2) { // resp from MF or a DF
            
            int free_memory = resp.getByte(2)*256+resp.getByte(3);
            
            byte [] fid = new byte[2];
            fid[0] = (byte) resp.getByte(4);
            fid[1] = (byte) resp.getByte(5);
            
            int data_length = resp.getByte(12);
            int clockstop = resp.getByte(13)&13;
                /*
                 * clockstop values:
                 * 1: cs allowed, no preferred level
                 * 5: cs allowed, high level preferred
                 * 9: cs allowed, low level preferred
                 * 0: cs not allowd
                 * 4: cs not allowed, unless at high level
                 * 8: cs not allowed, unless at low level
                 */
            int df_count = resp.getByte(14);
            int ef_count = resp.getByte(15);
            int no_of_chvs = resp.getByte(16);
            
            FileProperties fp = new FileProperties(free_memory, 0x38, data_length, 0, fid, (byte)0x00, (byte)0x0F);
            return fp;
        } else return null;
    }
    
    /** This method implements the method of JSmexCardService.
     *  It checks if there is an EF or DF with the fid, which comes in fidString,
     *  the currently selected DF. If a file was found, the method returns a new SmartCardFile
     *  instance for the founded file. Else it return null.
     *
     * @param fidString a String which contains the FID
     * @return a instance of SmartCardFile or null
     */
    public SmartCardFile getSmartCardFile(String fidString) {
                
        byte[] fid = JSmexTools.parseHexString(fidString);
        
        ResponseAPDU resp = selectDF(fid);
        String name;
        
        if (resp.sw()!=0x9000) {
            resp = selectEF(fid);
            if (resp.sw()!=0x9000) {
                lt.info("called method getSmartCardFile(fidString: "+fidString+"). returns: 'null'",this);
                return null;
            }
            
        }
        
        FileProperties fp = getProperties(resp);
        
        if (fp.isDF()) name = new String("DF ("+fidString+")");
        else name = new String("EF ("+fidString+")");
        
        SmartCardFile scfile = new SmartCardFile(name ,fp);
        lt.info("called method getSmartCardFile(fidString: "+fidString+"). returns new SmartCardFile with the name: "+name ,this);
        return scfile;
    }
    
    /**This method implements the method of JSmexCardService.
     * It updates one complete record in the current linear or cyclic EF.
     * @param recno record number to update
     * @param data data to write
     * @return ResponseAPDU
     */
    public ResponseAPDU updateRecord(byte recno, byte[] data) {
        byte[] ur_cmd = { (byte)0xA0, (byte)0xDC, (byte)recno, (byte)0x04, (byte)data.length};
        command.setLength(0);
        command.append(ur_cmd);
        command.append(data);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method updateRecord(byte recno, byte[] data) throws a CardTerminalException.",this);
        }
        lt.info("called method updateRecord((byte recno, byte[] data). returns SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
}
